const ns = angular.module('ng.select', []);

ns.directive('mySelect', function ($parse, $document, $compile) {
    //from bootstrap-ui typeahead parser
    const TYPEAHEAD_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+(.*)$/;
    let optionParser = {
        parse: function (input) {

            const match = input.match(TYPEAHEAD_REGEXP);
            let modelMapper, viewMapper, source;
            if (!match) {
                throw new Error(
                    "Expected typeahead specification in form of '_modelValue_ (as _label_)? for _item_ in _collection_'" +
                    " but got '" + input + "'.");
            }

            return {
                itemName: match[3],
                source: $parse(match[4]),
                viewMapper: $parse(match[2] || match[1]),
                modelMapper: $parse(match[1])
            };
        }
    };

    return {
        restrict: 'E',
        require: 'ngModel',
        link: function (originalScope, element, attrs, modelCtrl) {

            const exp = attrs.options,
                parsedResult = optionParser.parse(exp);
            let isMultiple = !!attrs.multiple;
            const checkAllName = attrs.checkAllName;
            let required = false;
            const scope = originalScope.$new(),
                changeHandler = attrs.change || angular.noop;

            scope.items = [];
            scope.header = 'Select';
            scope.multiple = isMultiple;
            scope.disabled = false;
            scope.allChecked = false;
            scope.checkAllName = checkAllName || 'Check all';
            scope.valid = function validModel() {
                if(!required) return true;
                const value = modelCtrl.$modelValue;
                return (angular.isArray(value) && value.length > 0) || (!angular.isArray(value) && value != null);
            };

            originalScope.$on('$destroy', function () {
                scope.$destroy();
            });

            const popUpEl = angular.element('<my-select-popup></my-select-popup>');

            //required validator
            if (attrs.required || attrs.ngRequired) {
                required = true;
            }
            attrs.$observe('required', function(newVal) {
                required = newVal;
            });

            //watch disabled state
            scope.$watch(function () {
                return $parse(attrs.disabled)(originalScope);
            }, function (newVal) {
                scope.disabled = newVal;
            });

            //watch single/multiple state for dynamically change single to multiple
            scope.$watch(function () {
                return $parse(attrs.multiple)(originalScope);
            }, function (newVal) {
                isMultiple = newVal || false;
            });

            //watch option changes for options that are populated dynamically
            scope.$watch(function () {
                return parsedResult.source(originalScope);
            }, function (newVal) {
                if (angular.isDefined(newVal))
                    parseModel();
            });

            //watch model change
            scope.$watch(function () {
                return modelCtrl.$modelValue;
            }, function (newVal, oldVal) {
                //when directive initialize, newVal usually undefined. Also, if model value already set in the controller
                //for preselected list then we need to mark checked in our scope item. But we don't want to do this every time
                //model changes. We need to do this only if it is done outside directive scope, from controller, for example.
                if (angular.isDefined(newVal)) {
                    markChecked(newVal);
                    scope.$eval(changeHandler);
                }
                getHeaderText();
                modelCtrl.$setValidity('required', scope.valid());
            }, true);

            function parseModel() {
                scope.items.length = 0;
                const model = parsedResult.source(originalScope);
                for (let i = 0; i < model.length; i++) {
                    const local = {};
                    local[parsedResult.itemName] = model[i];
                    scope.items.push({
                        label: parsedResult.viewMapper(local),
                        model: model[i],
                        checked: false
                    });
                }
            }

            parseModel();

            element.append($compile(popUpEl)(scope));

            function getHeaderText() {
                if (!modelCtrl.$modelValue/* || !modelCtrl.$modelValue.length*/) return scope.header = 'Select';
                if (isMultiple) {
                    if (!modelCtrl.$modelValue.length) return scope.header = 'Select';
                    // scope.header = modelCtrl.$modelValue.length + ' ' + 'selected';
                    if (scope.allChecked && checkAllName) {
                        scope.header = checkAllName
                    } else {
                        let values = [];
                        for (let i = 0; i < modelCtrl.$modelValue.length; i++) {
                            let local = {};
                            local[parsedResult.itemName] = modelCtrl.$modelValue[i];
                            values.push(parsedResult.viewMapper(local));
                        }
                        scope.header = values.join(',')
                    }
                } else {
                    var local = {};
                    local[parsedResult.itemName] = modelCtrl.$modelValue;
                    scope.header = parsedResult.viewMapper(local);
                }
            }

            function selectSingle(item) {
                if (item.checked) {
                    scope.uncheckAll();
                } else {
                    scope.uncheckAll();
                    item.checked = !item.checked;
                }
                setModelValue(false);
            }

            function selectMultiple(item) {
                item.checked = !item.checked;
                setModelValue(true);
            }

            function setModelValue(isMultiple) {
                let value;

                if (isMultiple) {
                    value = [];
                    angular.forEach(scope.items, function (item) {
                        if (item.checked) value.push(item.model);
                    })
                } else {
                    angular.forEach(scope.items, function (item) {
                        if (item.checked) {
                            value = item.model;
                            return false;
                        }
                    })
                }
                modelCtrl.$setViewValue(value);
            }

            function markChecked(newVal) {
                if (!angular.isArray(newVal)) {
                    angular.forEach(scope.items, function (item) {
                        if (angular.equals(item.model, newVal)) {
                            item.checked = true;
                            return false;
                        }
                    });
                } else {
                    angular.forEach(scope.items, function (item) {
                        item.checked = false;
                        for (let i = 0; i < newVal.length; i++) {
                            if (angular.equals(item.model, newVal[i])) {
                                item.checked = true;
                                break;
                            }
                        }
                    });
                }
            }

            function updateCheckAllState() {
                scope.allChecked = scope.items.length === modelCtrl.$modelValue.length
            }

            scope.checkAll = function () {
                scope.allChecked = !scope.allChecked;
                if (scope.allChecked) {
                    if (!isMultiple) return;
                    angular.forEach(scope.items, function (item) {
                        item.checked = true;
                    });
                    setModelValue(true);
                } else {
                    scope.uncheckAll()
                }
            };

            scope.uncheckAll = function () {
                angular.forEach(scope.items, function (item) {
                    item.checked = false;
                });
                scope.allChecked = false;
                setModelValue(true);
            };

            scope.select = function (item) {
                if (isMultiple === false) {
                    selectSingle(item);
                    scope.toggleSelect();
                } else {
                    selectMultiple(item);
                }
                updateCheckAllState()
            }
        }
    };
});

ns.directive('mySelectPopup', ['$document', function ($document) {
    const tpl = '<div class="my-select"><style> .my-select {display: block;position: relative;width: 200px;font-size: 14px;z-index: 100;}  .my-select * {box-sizing: border-box;}  .my-select-selection, .my-select-selection_choice, .my-select-dropdown-menu {width: 100%;}  .my-select-selection_choice {height: 34px;line-height: 34px;padding: 0 24px 0 8px;border: 1px solid rgb(217, 217, 217);border-radius: 2px;background-color: #fff;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;}  .my-select-selection_choice:hover {background-color: #e6e6e6;}  .my-select-icon {position: absolute;top: 0;right: 0;line-height: 34px;}  .my-select-icon > * {line-height: 34px;}  .my-select-icon:last-child {margin-right: 4px;}  /*删除图标*/  .my-select-selection_choice_remove {position: relative;display: none;width: 1em;height: 1em;cursor: pointer;}  .my-select-selection:hover .my-select-selection_choice_remove {display: inline-block;}  .my-select-selection:hover .my-select-selection_choice_remove:before, .my-select-selection:hover .my-select-selection_choice_remove:after{content: \'\';position: absolute;top: .1em;left: -.2em;width: 1px;height: 1em;background: rgb(51, 51, 51);}  .my-select-selection:hover .my-select-selection_choice_remove:after {transform: rotate(45deg);}  .my-select-selection:hover .my-select-selection_choice_remove:before {transform: rotate(-45deg);}  /*下拉箭头*/  .my-select-arrow-icon_pull-down {cursor: pointer;display: inline-block;width: 0.7em;height: 0.7em;border-left: 1px solid rgb(51, 51, 51);border-bottom: 1px solid rgb(51, 51, 51);transform: rotate(-45deg) translateY(-0.3em);}  .my-select-dropdown-menu {position: absolute;display: none;max-height: 200px;overflow-y: auto;margin: 0;padding: 0;border: 1px solid rgba(0, 0, 0, .15);border-radius: 4px;background: white;box-shadow: 0 6px 12px rgba(0, 0, 0, .175);}  .my-select.open > .my-select-dropdown-menu {display: block;}  .my-select-dropdown-menu-search {width: 100%;margin: 10px 0;padding: 0 10px;}  .my-select-dropdown-menu-search-box {width: 100%;height: 28px;padding: 0 8px;line-height: 28px;border: 1px solid rgb(217, 217, 217);border-radius: 2px;}  .my-select-dropdown-menu-item {width: 100%;margin: 4px 0;list-style: none;}  .my-select-dropdown-menu-item:hover {background: #EEE;}  .my-select-dropdown-menu-item_render {display: flex;width: 100%;padding: 4px 10px;align-items: center;}  .my-select-dropdown-menu-item_render-icon-wrapper {display: flex;align-items: center;position: relative;margin-right: 4px;}  .my-select-dropdown-menu-item_render-icon {border: 1px solid limegreen;border-radius: 2px;display: inline-block;width: 1.2em;height: 1.2em;}  .my-select-dropdown-menu-item_render-icon.checked {border: transparent;background: limegreen;}  .my-select-dropdown-menu-item_render-icon.checked:before {content: \'\';display: block;width: 68%;height: 31%;margin: 20% auto;border-left: 2px solid white;border-bottom: 2px solid white;transform: rotate(-45deg);}  .my-select-dropdown-menu.single .my-select-dropdown-menu-item_render-icon {border: none;}  .my-select-dropdown-menu.single .my-select-dropdown-menu-item_render-icon.checked {background: none;}  .my-select-dropdown-menu.single .my-select-dropdown-menu-item_render-icon.checked:before {border-left: 2px solid limegreen;border-bottom: 2px solid limegreen;}  .my-select-dropdown-menu-item-text {overflow: hidden;text-overflow: ellipsis;white-space: nowrap;}</style><div class="my-select-selection" ng-disabled="disabled"><input class="my-select-selection_choice ellipsis" title="{{header}}" ng-click="toggleSelect()" ng-model="header"><span class="my-select-icon"><i class="my-select-selection_choice_remove" ng-click="uncheckAll()"></i><i class="my-select-arrow-icon_pull-down" ng-click="toggleSelect()"></i></span></div><ul class="my-select-dropdown-menu" ng-class="{\'single\': !multiple}" ng-show="items.length"><li class="my-select-dropdown-menu-search"><input class="my-select-dropdown-menu-search-box" type="text" ng-model="searchText.label" autofocus="autofocus" placeholder="Filter"/></li><li class="my-select-dropdown-menu-item" ng-show="multiple"><div class="my-select-dropdown-menu-item_render" ng-click="checkAll()"><div class="my-select-dropdown-menu-item_render-icon-wrapper"><i class="my-select-dropdown-menu-item_render-icon" ng-class="{\'checked\':allChecked}"></i></div><div class="my-select-dropdown-menu-item-text" title="{{checkAllName}}">{{checkAllName}}</div></div><!--<button class="btn-link btn-small" ng-click="uncheckAll()"><i class="icon-remove"></i> Uncheck all</button>--></li><li class="my-select-dropdown-menu-item" ng-repeat="i in items | filter:searchText"><div class="my-select-dropdown-menu-item_render" ng-click="select(i); focus()"><div class="my-select-dropdown-menu-item_render-icon-wrapper"><i class="my-select-dropdown-menu-item_render-icon" ng-class="{\'checked\': i.checked}"></i></div><div class="my-select-dropdown-menu-item-text" title="{{i.label}}">{{i.label}}</div></div></li></ul></div>';

    return {
        restrict: 'E',
        scope: false,
        replace: true,
        template: tpl,
        link: function (scope, element, attrs) {

            scope.isVisible = false;

            scope.toggleSelect = function () {
                if (element.hasClass('open')) {
                    element.removeClass('open');
                    $document.unbind('click', clickHandler);
                } else {
                    element.addClass('open');
                    scope.focus();
                    $document.bind('click', clickHandler);
                }
            };

            function clickHandler(event) {
                if (elementMatchesAnyInArray(event.target, element.find(event.target.tagName)))
                    return;
                element.removeClass('open');
                $document.unbind('click', clickHandler);
                scope.$digest();
            }

            scope.focus = function focus(){
                const searchBox = element.find('input')[0];
                searchBox.focus();
            };

            var elementMatchesAnyInArray = function (element, elementArray) {
                for (let i = 0; i < elementArray.length; i++)
                    if (element === elementArray[i])
                        return true;
                return false;
            }
        }
    }
}]);